/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.swing.plaf.basic;

import sun.swing.DefaultLookup;
import sun.swing.UIAction;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.plaf.ActionMapUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.OptionPaneUI;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Locale;
import java.security.AccessController;

import sun.security.action.GetPropertyAction;


/**
 * Provides the basic look and feel for a <code>JOptionPane</code>.
 * <code>BasicMessagePaneUI</code> provides a means to place an icon,
 * message and buttons into a <code>Container</code>.
 * Generally, the layout will look like:
 * <pre>
 *        ------------------
 *        | i | message    |
 *        | c | message    |
 *        | o | message    |
 *        | n | message    |
 *        ------------------
 *        |     buttons    |
 *        |________________|
 * </pre>
 * icon is an instance of <code>Icon</code> that is wrapped inside a
 * <code>JLabel</code>.  The message is an opaque object and is tested
 * for the following: if the message is a <code>Component</code> it is
 * added to the <code>Container</code>, if it is an <code>Icon</code>
 * it is wrapped inside a <code>JLabel</code> and added to the
 * <code>Container</code> otherwise it is wrapped inside a <code>JLabel</code>.
 * <p>
 * The above layout is used when the option pane's
 * <code>ComponentOrientation</code> property is horizontal, left-to-right.
 * The layout will be adjusted appropriately for other orientations.
 * <p>
 * The <code>Container</code>, message, icon, and buttons are all
 * determined from abstract methods.
 *
 * @author James Gosling
 * @author Scott Violet
 * @author Amy Fowler
 */
public class BasicOptionPaneUI extends OptionPaneUI {

  public static final int MinimumWidth = 262;
  public static final int MinimumHeight = 90;

  private static String newline;

  /**
   * <code>JOptionPane</code> that the receiver is providing the
   * look and feel for.
   */
  protected JOptionPane optionPane;

  protected Dimension minimumSize;

  /**
   * JComponent provide for input if optionPane.getWantsInput() returns
   * true.
   */
  protected JComponent inputComponent;

  /**
   * Component to receive focus when messaged with selectInitialValue.
   */
  protected Component initialFocusComponent;

  /**
   * This is set to true in validateComponent if a Component is contained
   * in either the message or the buttons.
   */
  protected boolean hasCustomComponents;

  protected PropertyChangeListener propertyChangeListener;

  private Handler handler;


  static {
    newline = java.security.AccessController.doPrivileged(
        new GetPropertyAction("line.separator"));
    if (newline == null) {
      newline = "\n";
    }
  }

  static void loadActionMap(LazyActionMap map) {
    map.put(new Actions(Actions.CLOSE));
    BasicLookAndFeel.installAudioActionMap(map);
  }


  /**
   * Creates a new BasicOptionPaneUI instance.
   */
  public static ComponentUI createUI(JComponent x) {
    return new BasicOptionPaneUI();
  }

  /**
   * Installs the receiver as the L&amp;F for the passed in
   * <code>JOptionPane</code>.
   */
  public void installUI(JComponent c) {
    optionPane = (JOptionPane) c;
    installDefaults();
    optionPane.setLayout(createLayoutManager());
    installComponents();
    installListeners();
    installKeyboardActions();
  }

  /**
   * Removes the receiver from the L&amp;F controller of the passed in split
   * pane.
   */
  public void uninstallUI(JComponent c) {
    uninstallComponents();
    optionPane.setLayout(null);
    uninstallKeyboardActions();
    uninstallListeners();
    uninstallDefaults();
    optionPane = null;
  }

  protected void installDefaults() {
    LookAndFeel.installColorsAndFont(optionPane, "OptionPane.background",
        "OptionPane.foreground", "OptionPane.font");
    LookAndFeel.installBorder(optionPane, "OptionPane.border");
    minimumSize = UIManager.getDimension("OptionPane.minimumSize");
    LookAndFeel.installProperty(optionPane, "opaque", Boolean.TRUE);
  }

  protected void uninstallDefaults() {
    LookAndFeel.uninstallBorder(optionPane);
  }

  protected void installComponents() {
    optionPane.add(createMessageArea());

    Container separator = createSeparator();
    if (separator != null) {
      optionPane.add(separator);
    }
    optionPane.add(createButtonArea());
    optionPane.applyComponentOrientation(optionPane.getComponentOrientation());
  }

  protected void uninstallComponents() {
    hasCustomComponents = false;
    inputComponent = null;
    initialFocusComponent = null;
    optionPane.removeAll();
  }

  protected LayoutManager createLayoutManager() {
    return new BoxLayout(optionPane, BoxLayout.Y_AXIS);
  }

  protected void installListeners() {
    if ((propertyChangeListener = createPropertyChangeListener()) != null) {
      optionPane.addPropertyChangeListener(propertyChangeListener);
    }
  }

  protected void uninstallListeners() {
    if (propertyChangeListener != null) {
      optionPane.removePropertyChangeListener(propertyChangeListener);
      propertyChangeListener = null;
    }
    handler = null;
  }

  protected PropertyChangeListener createPropertyChangeListener() {
    return getHandler();
  }

  private Handler getHandler() {
    if (handler == null) {
      handler = new Handler();
    }
    return handler;
  }

  protected void installKeyboardActions() {
    InputMap map = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);

    SwingUtilities.replaceUIInputMap(optionPane, JComponent.
        WHEN_IN_FOCUSED_WINDOW, map);

    LazyActionMap.installLazyActionMap(optionPane, BasicOptionPaneUI.class,
        "OptionPane.actionMap");
  }

  protected void uninstallKeyboardActions() {
    SwingUtilities.replaceUIInputMap(optionPane, JComponent.
        WHEN_IN_FOCUSED_WINDOW, null);
    SwingUtilities.replaceUIActionMap(optionPane, null);
  }

  InputMap getInputMap(int condition) {
    if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) {
      Object[] bindings = (Object[]) DefaultLookup.get(
          optionPane, this, "OptionPane.windowBindings");
      if (bindings != null) {
        return LookAndFeel.makeComponentInputMap(optionPane, bindings);
      }
    }
    return null;
  }

  /**
   * Returns the minimum size the option pane should be. Primarily
   * provided for subclassers wishing to offer a different minimum size.
   */
  public Dimension getMinimumOptionPaneSize() {
    if (minimumSize == null) {
      return new Dimension(MinimumWidth, MinimumHeight);
    }
    return new Dimension(minimumSize.width,
        minimumSize.height);
  }

  /**
   * If <code>c</code> is the <code>JOptionPane</code> the receiver
   * is contained in, the preferred
   * size that is returned is the maximum of the preferred size of
   * the <code>LayoutManager</code> for the <code>JOptionPane</code>, and
   * <code>getMinimumOptionPaneSize</code>.
   */
  public Dimension getPreferredSize(JComponent c) {
    if (c == optionPane) {
      Dimension ourMin = getMinimumOptionPaneSize();
      LayoutManager lm = c.getLayout();

      if (lm != null) {
        Dimension lmSize = lm.preferredLayoutSize(c);

        if (ourMin != null) {
          return new Dimension
              (Math.max(lmSize.width, ourMin.width),
                  Math.max(lmSize.height, ourMin.height));
        }
        return lmSize;
      }
      return ourMin;
    }
    return null;
  }

  /**
   * Messaged from installComponents to create a Container containing the
   * body of the message. The icon is the created by calling
   * <code>addIcon</code>.
   */
  protected Container createMessageArea() {
    JPanel top = new JPanel();
    Border topBorder = (Border) DefaultLookup.get(optionPane, this,
        "OptionPane.messageAreaBorder");
    if (topBorder != null) {
      top.setBorder(topBorder);
    }
    top.setLayout(new BorderLayout());

        /* Fill the body. */
    Container body = new JPanel(new GridBagLayout());
    Container realBody = new JPanel(new BorderLayout());

    body.setName("OptionPane.body");
    realBody.setName("OptionPane.realBody");

    if (getIcon() != null) {
      JPanel sep = new JPanel();
      sep.setName("OptionPane.separator");
      sep.setPreferredSize(new Dimension(15, 1));
      realBody.add(sep, BorderLayout.BEFORE_LINE_BEGINS);
    }
    realBody.add(body, BorderLayout.CENTER);

    GridBagConstraints cons = new GridBagConstraints();
    cons.gridx = cons.gridy = 0;
    cons.gridwidth = GridBagConstraints.REMAINDER;
    cons.gridheight = 1;
    cons.anchor = DefaultLookup.getInt(optionPane, this,
        "OptionPane.messageAnchor", GridBagConstraints.CENTER);
    cons.insets = new Insets(0, 0, 3, 0);

    addMessageComponents(body, cons, getMessage(),
        getMaxCharactersPerLineCount(), false);
    top.add(realBody, BorderLayout.CENTER);

    addIcon(top);
    return top;
  }

  /**
   * Creates the appropriate object to represent <code>msg</code> and
   * places it into <code>container</code>. If <code>msg</code> is an
   * instance of Component, it is added directly, if it is an Icon,
   * a JLabel is created to represent it, otherwise a JLabel is
   * created for the string, if <code>d</code> is an Object[], this
   * method will be recursively invoked for the children.
   * <code>internallyCreated</code> is true if Objc is an instance
   * of Component and was created internally by this method (this is
   * used to correctly set hasCustomComponents only if !internallyCreated).
   */
  protected void addMessageComponents(Container container,
      GridBagConstraints cons,
      Object msg, int maxll,
      boolean internallyCreated) {
    if (msg == null) {
      return;
    }
    if (msg instanceof Component) {
      // To workaround problem where Gridbad will set child
      // to its minimum size if its preferred size will not fit
      // within allocated cells
      if (msg instanceof JScrollPane || msg instanceof JPanel) {
        cons.fill = GridBagConstraints.BOTH;
        cons.weighty = 1;
      } else {
        cons.fill = GridBagConstraints.HORIZONTAL;
      }
      cons.weightx = 1;

      container.add((Component) msg, cons);
      cons.weightx = 0;
      cons.weighty = 0;
      cons.fill = GridBagConstraints.NONE;
      cons.gridy++;
      if (!internallyCreated) {
        hasCustomComponents = true;
      }

    } else if (msg instanceof Object[]) {
      Object[] msgs = (Object[]) msg;
      for (Object o : msgs) {
        addMessageComponents(container, cons, o, maxll, false);
      }

    } else if (msg instanceof Icon) {
      JLabel label = new JLabel((Icon) msg, SwingConstants.CENTER);
      configureMessageLabel(label);
      addMessageComponents(container, cons, label, maxll, true);

    } else {
      String s = msg.toString();
      int len = s.length();
      if (len <= 0) {
        return;
      }
      int nl;
      int nll = 0;

      if ((nl = s.indexOf(newline)) >= 0) {
        nll = newline.length();
      } else if ((nl = s.indexOf("\r\n")) >= 0) {
        nll = 2;
      } else if ((nl = s.indexOf('\n')) >= 0) {
        nll = 1;
      }
      if (nl >= 0) {
        // break up newlines
        if (nl == 0) {
          JPanel breakPanel = new JPanel() {
            public Dimension getPreferredSize() {
              Font f = getFont();

              if (f != null) {
                return new Dimension(1, f.getSize() + 2);
              }
              return new Dimension(0, 0);
            }
          };
          breakPanel.setName("OptionPane.break");
          addMessageComponents(container, cons, breakPanel, maxll,
              true);
        } else {
          addMessageComponents(container, cons, s.substring(0, nl),
              maxll, false);
        }
        addMessageComponents(container, cons, s.substring(nl + nll), maxll,
            false);

      } else if (len > maxll) {
        Container c = Box.createVerticalBox();
        c.setName("OptionPane.verticalBox");
        burstStringInto(c, s, maxll);
        addMessageComponents(container, cons, c, maxll, true);

      } else {
        JLabel label;
        label = new JLabel(s, JLabel.LEADING);
        label.setName("OptionPane.label");
        configureMessageLabel(label);
        addMessageComponents(container, cons, label, maxll, true);
      }
    }
  }

  /**
   * Returns the message to display from the JOptionPane the receiver is
   * providing the look and feel for.
   */
  protected Object getMessage() {
    inputComponent = null;
    if (optionPane != null) {
      if (optionPane.getWantsInput()) {
                /* Create a user component to capture the input. If the
                   selectionValues are non null the component and there
                   are < 20 values it'll be a combobox, if non null and
                   >= 20, it'll be a list, otherwise it'll be a textfield. */
        Object message = optionPane.getMessage();
        Object[] sValues = optionPane.getSelectionValues();
        Object inputValue = optionPane
            .getInitialSelectionValue();
        JComponent toAdd;

        if (sValues != null) {
          if (sValues.length < 20) {
            JComboBox cBox = new JComboBox();

            cBox.setName("OptionPane.comboBox");
            for (int counter = 0, maxCounter = sValues.length;
                counter < maxCounter; counter++) {
              cBox.addItem(sValues[counter]);
            }
            if (inputValue != null) {
              cBox.setSelectedItem(inputValue);
            }
            inputComponent = cBox;
            toAdd = cBox;

          } else {
            JList list = new JList(sValues);
            JScrollPane sp = new JScrollPane(list);

            sp.setName("OptionPane.scrollPane");
            list.setName("OptionPane.list");
            list.setVisibleRowCount(10);
            list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            if (inputValue != null) {
              list.setSelectedValue(inputValue, true);
            }
            list.addMouseListener(getHandler());
            toAdd = sp;
            inputComponent = list;
          }

        } else {
          MultiplexingTextField tf = new MultiplexingTextField(20);

          tf.setName("OptionPane.textField");
          tf.setKeyStrokes(new KeyStroke[]{
              KeyStroke.getKeyStroke("ENTER")});
          if (inputValue != null) {
            String inputString = inputValue.toString();
            tf.setText(inputString);
            tf.setSelectionStart(0);
            tf.setSelectionEnd(inputString.length());
          }
          tf.addActionListener(getHandler());
          toAdd = inputComponent = tf;
        }

        Object[] newMessage;

        if (message == null) {
          newMessage = new Object[1];
          newMessage[0] = toAdd;

        } else {
          newMessage = new Object[2];
          newMessage[0] = message;
          newMessage[1] = toAdd;
        }
        return newMessage;
      }
      return optionPane.getMessage();
    }
    return null;
  }

  /**
   * Creates and adds a JLabel representing the icon returned from
   * <code>getIcon</code> to <code>top</code>. This is messaged from
   * <code>createMessageArea</code>
   */
  protected void addIcon(Container top) {
        /* Create the icon. */
    Icon sideIcon = getIcon();

    if (sideIcon != null) {
      JLabel iconLabel = new JLabel(sideIcon);

      iconLabel.setName("OptionPane.iconLabel");
      iconLabel.setVerticalAlignment(SwingConstants.TOP);
      top.add(iconLabel, BorderLayout.BEFORE_LINE_BEGINS);
    }
  }

  /**
   * Returns the icon from the JOptionPane the receiver is providing
   * the look and feel for, or the default icon as returned from
   * <code>getDefaultIcon</code>.
   */
  protected Icon getIcon() {
    Icon mIcon = (optionPane == null ? null : optionPane.getIcon());

    if (mIcon == null && optionPane != null) {
      mIcon = getIconForType(optionPane.getMessageType());
    }
    return mIcon;
  }

  /**
   * Returns the icon to use for the passed in type.
   */
  protected Icon getIconForType(int messageType) {
    if (messageType < 0 || messageType > 3) {
      return null;
    }
    String propertyName = null;
    switch (messageType) {
      case 0:
        propertyName = "OptionPane.errorIcon";
        break;
      case 1:
        propertyName = "OptionPane.informationIcon";
        break;
      case 2:
        propertyName = "OptionPane.warningIcon";
        break;
      case 3:
        propertyName = "OptionPane.questionIcon";
        break;
    }
    if (propertyName != null) {
      return (Icon) DefaultLookup.get(optionPane, this, propertyName);
    }
    return null;
  }

  /**
   * Returns the maximum number of characters to place on a line.
   */
  protected int getMaxCharactersPerLineCount() {
    return optionPane.getMaxCharactersPerLineCount();
  }

  /**
   * Recursively creates new JLabel instances to represent <code>d</code>.
   * Each JLabel instance is added to <code>c</code>.
   */
  protected void burstStringInto(Container c, String d, int maxll) {
    // Primitive line wrapping
    int len = d.length();
    if (len <= 0) {
      return;
    }
    if (len > maxll) {
      int p = d.lastIndexOf(' ', maxll);
      if (p <= 0) {
        p = d.indexOf(' ', maxll);
      }
      if (p > 0 && p < len) {
        burstStringInto(c, d.substring(0, p), maxll);
        burstStringInto(c, d.substring(p + 1), maxll);
        return;
      }
    }
    JLabel label = new JLabel(d, JLabel.LEFT);
    label.setName("OptionPane.label");
    configureMessageLabel(label);
    c.add(label);
  }

  protected Container createSeparator() {
    return null;
  }

  /**
   * Creates and returns a Container containing the buttons. The buttons
   * are created by calling <code>getButtons</code>.
   */
  protected Container createButtonArea() {
    JPanel bottom = new JPanel();
    Border border = (Border) DefaultLookup.get(optionPane, this,
        "OptionPane.buttonAreaBorder");
    bottom.setName("OptionPane.buttonArea");
    if (border != null) {
      bottom.setBorder(border);
    }
    bottom.setLayout(new ButtonAreaLayout(
        DefaultLookup.getBoolean(optionPane, this,
            "OptionPane.sameSizeButtons", true),
        DefaultLookup.getInt(optionPane, this, "OptionPane.buttonPadding",
            6),
        DefaultLookup.getInt(optionPane, this,
            "OptionPane.buttonOrientation", SwingConstants.CENTER),
        DefaultLookup.getBoolean(optionPane, this, "OptionPane.isYesLast",
            false)));
    addButtonComponents(bottom, getButtons(), getInitialValueIndex());
    return bottom;
  }

  /**
   * Creates the appropriate object to represent each of the objects in
   * <code>buttons</code> and adds it to <code>container</code>. This
   * differs from addMessageComponents in that it will recurse on
   * <code>buttons</code> and that if button is not a Component
   * it will create an instance of JButton.
   */
  protected void addButtonComponents(Container container, Object[] buttons,
      int initialIndex) {
    if (buttons != null && buttons.length > 0) {
      boolean sizeButtonsToSame = getSizeButtonsToSameWidth();
      boolean createdAll = true;
      int numButtons = buttons.length;
      JButton[] createdButtons = null;
      int maxWidth = 0;

      if (sizeButtonsToSame) {
        createdButtons = new JButton[numButtons];
      }

      for (int counter = 0; counter < numButtons; counter++) {
        Object button = buttons[counter];
        Component newComponent;

        if (button instanceof Component) {
          createdAll = false;
          newComponent = (Component) button;
          container.add(newComponent);
          hasCustomComponents = true;

        } else {
          JButton aButton;

          if (button instanceof ButtonFactory) {
            aButton = ((ButtonFactory) button).createButton();
          } else if (button instanceof Icon) {
            aButton = new JButton((Icon) button);
          } else {
            aButton = new JButton(button.toString());
          }

          aButton.setName("OptionPane.button");
          aButton.setMultiClickThreshhold(DefaultLookup.getInt(
              optionPane, this, "OptionPane.buttonClickThreshhold",
              0));
          configureButton(aButton);

          container.add(aButton);

          ActionListener buttonListener = createButtonActionListener(counter);
          if (buttonListener != null) {
            aButton.addActionListener(buttonListener);
          }
          newComponent = aButton;
        }
        if (sizeButtonsToSame && createdAll &&
            (newComponent instanceof JButton)) {
          createdButtons[counter] = (JButton) newComponent;
          maxWidth = Math.max(maxWidth,
              newComponent.getMinimumSize().width);
        }
        if (counter == initialIndex) {
          initialFocusComponent = newComponent;
          if (initialFocusComponent instanceof JButton) {
            JButton defaultB = (JButton) initialFocusComponent;
            defaultB.addHierarchyListener(new HierarchyListener() {
              public void hierarchyChanged(HierarchyEvent e) {
                if ((e.getChangeFlags() &
                    HierarchyEvent.PARENT_CHANGED) != 0) {
                  JButton defaultButton = (JButton) e.getComponent();
                  JRootPane root =
                      SwingUtilities.getRootPane(defaultButton);
                  if (root != null) {
                    root.setDefaultButton(defaultButton);
                  }
                }
              }
            });
          }
        }
      }
      ((ButtonAreaLayout) container.getLayout()).
          setSyncAllWidths((sizeButtonsToSame && createdAll));
            /* Set the padding, windows seems to use 8 if <= 2 components,
               otherwise 4 is used. It may actually just be the size of the
               buttons is always the same, not sure. */
      if (DefaultLookup.getBoolean(optionPane, this,
          "OptionPane.setButtonMargin", true) && sizeButtonsToSame &&
          createdAll) {
        JButton aButton;
        int padSize;

        padSize = (numButtons <= 2 ? 8 : 4);

        for (int counter = 0; counter < numButtons; counter++) {
          aButton = createdButtons[counter];
          aButton.setMargin(new Insets(2, padSize, 2, padSize));
        }
      }
    }
  }

  protected ActionListener createButtonActionListener(int buttonIndex) {
    return new ButtonActionListener(buttonIndex);
  }

  /**
   * Returns the buttons to display from the JOptionPane the receiver is
   * providing the look and feel for. If the JOptionPane has options
   * set, they will be provided, otherwise if the optionType is
   * YES_NO_OPTION, yesNoOptions is returned, if the type is
   * YES_NO_CANCEL_OPTION yesNoCancelOptions is returned, otherwise
   * defaultButtons are returned.
   */
  protected Object[] getButtons() {
    if (optionPane != null) {
      Object[] suppliedOptions = optionPane.getOptions();

      if (suppliedOptions == null) {
        Object[] defaultOptions;
        int type = optionPane.getOptionType();
        Locale l = optionPane.getLocale();
        int minimumWidth =
            DefaultLookup.getInt(optionPane, this,
                "OptionPane.buttonMinimumWidth", -1);
        if (type == JOptionPane.YES_NO_OPTION) {
          defaultOptions = new ButtonFactory[2];
          defaultOptions[0] = new ButtonFactory(
              UIManager.getString("OptionPane.yesButtonText", l),
              getMnemonic("OptionPane.yesButtonMnemonic", l),
              (Icon) DefaultLookup.get(optionPane, this,
                  "OptionPane.yesIcon"), minimumWidth);
          defaultOptions[1] = new ButtonFactory(
              UIManager.getString("OptionPane.noButtonText", l),
              getMnemonic("OptionPane.noButtonMnemonic", l),
              (Icon) DefaultLookup.get(optionPane, this,
                  "OptionPane.noIcon"), minimumWidth);
        } else if (type == JOptionPane.YES_NO_CANCEL_OPTION) {
          defaultOptions = new ButtonFactory[3];
          defaultOptions[0] = new ButtonFactory(
              UIManager.getString("OptionPane.yesButtonText", l),
              getMnemonic("OptionPane.yesButtonMnemonic", l),
              (Icon) DefaultLookup.get(optionPane, this,
                  "OptionPane.yesIcon"), minimumWidth);
          defaultOptions[1] = new ButtonFactory(
              UIManager.getString("OptionPane.noButtonText", l),
              getMnemonic("OptionPane.noButtonMnemonic", l),
              (Icon) DefaultLookup.get(optionPane, this,
                  "OptionPane.noIcon"), minimumWidth);
          defaultOptions[2] = new ButtonFactory(
              UIManager.getString("OptionPane.cancelButtonText", l),
              getMnemonic("OptionPane.cancelButtonMnemonic", l),
              (Icon) DefaultLookup.get(optionPane, this,
                  "OptionPane.cancelIcon"), minimumWidth);
        } else if (type == JOptionPane.OK_CANCEL_OPTION) {
          defaultOptions = new ButtonFactory[2];
          defaultOptions[0] = new ButtonFactory(
              UIManager.getString("OptionPane.okButtonText", l),
              getMnemonic("OptionPane.okButtonMnemonic", l),
              (Icon) DefaultLookup.get(optionPane, this,
                  "OptionPane.okIcon"), minimumWidth);
          defaultOptions[1] = new ButtonFactory(
              UIManager.getString("OptionPane.cancelButtonText", l),
              getMnemonic("OptionPane.cancelButtonMnemonic", l),
              (Icon) DefaultLookup.get(optionPane, this,
                  "OptionPane.cancelIcon"), minimumWidth);
        } else {
          defaultOptions = new ButtonFactory[1];
          defaultOptions[0] = new ButtonFactory(
              UIManager.getString("OptionPane.okButtonText", l),
              getMnemonic("OptionPane.okButtonMnemonic", l),
              (Icon) DefaultLookup.get(optionPane, this,
                  "OptionPane.okIcon"), minimumWidth);
        }
        return defaultOptions;

      }
      return suppliedOptions;
    }
    return null;
  }

  private int getMnemonic(String key, Locale l) {
    String value = (String) UIManager.get(key, l);

    if (value == null) {
      return 0;
    }
    try {
      return Integer.parseInt(value);
    } catch (NumberFormatException nfe) {
    }
    return 0;
  }

  /**
   * Returns true, basic L&amp;F wants all the buttons to have the same
   * width.
   */
  protected boolean getSizeButtonsToSameWidth() {
    return true;
  }

  /**
   * Returns the initial index into the buttons to select. The index
   * is calculated from the initial value from the JOptionPane and
   * options of the JOptionPane or 0.
   */
  protected int getInitialValueIndex() {
    if (optionPane != null) {
      Object iv = optionPane.getInitialValue();
      Object[] options = optionPane.getOptions();

      if (options == null) {
        return 0;
      } else if (iv != null) {
        for (int counter = options.length - 1; counter >= 0; counter--) {
          if (options[counter].equals(iv)) {
            return counter;
          }
        }
      }
    }
    return -1;
  }

  /**
   * Sets the input value in the option pane the receiver is providing
   * the look and feel for based on the value in the inputComponent.
   */
  protected void resetInputValue() {
    if (inputComponent != null && (inputComponent instanceof JTextField)) {
      optionPane.setInputValue(((JTextField) inputComponent).getText());

    } else if (inputComponent != null &&
        (inputComponent instanceof JComboBox)) {
      optionPane.setInputValue(((JComboBox) inputComponent)
          .getSelectedItem());
    } else if (inputComponent != null) {
      optionPane.setInputValue(((JList) inputComponent)
          .getSelectedValue());
    }
  }


  /**
   * If inputComponent is non-null, the focus is requested on that,
   * otherwise request focus on the default value
   */
  public void selectInitialValue(JOptionPane op) {
    if (inputComponent != null) {
      inputComponent.requestFocus();
    } else {
      if (initialFocusComponent != null) {
        initialFocusComponent.requestFocus();
      }

      if (initialFocusComponent instanceof JButton) {
        JRootPane root = SwingUtilities.getRootPane(initialFocusComponent);
        if (root != null) {
          root.setDefaultButton((JButton) initialFocusComponent);
        }
      }
    }
  }

  /**
   * Returns true if in the last call to validateComponent the message
   * or buttons contained a subclass of Component.
   */
  public boolean containsCustomComponents(JOptionPane op) {
    return hasCustomComponents;
  }


  /**
   * <code>ButtonAreaLayout</code> behaves in a similar manner to
   * <code>FlowLayout</code>. It lays out all components from left to
   * right. If <code>syncAllWidths</code> is true, the widths of each
   * component will be set to the largest preferred size width.
   *
   * This class should be treated as a &quot;protected&quot; inner class.
   * Instantiate it only within subclasses of {@code BasicOptionPaneUI}.
   */
  public static class ButtonAreaLayout implements LayoutManager {

    protected boolean syncAllWidths;
    protected int padding;
    /**
     * If true, children are lumped together in parent.
     */
    protected boolean centersChildren;
    private int orientation;
    private boolean reverseButtons;
    /**
     * Indicates whether or not centersChildren should be used vs
     * the orientation. This is done for backward compatibility
     * for subclassers.
     */
    private boolean useOrientation;

    public ButtonAreaLayout(boolean syncAllWidths, int padding) {
      this.syncAllWidths = syncAllWidths;
      this.padding = padding;
      centersChildren = true;
      useOrientation = false;
    }

    ButtonAreaLayout(boolean syncAllSizes, int padding, int orientation,
        boolean reverseButtons) {
      this(syncAllSizes, padding);
      useOrientation = true;
      this.orientation = orientation;
      this.reverseButtons = reverseButtons;
    }

    public void setSyncAllWidths(boolean newValue) {
      syncAllWidths = newValue;
    }

    public boolean getSyncAllWidths() {
      return syncAllWidths;
    }

    public void setPadding(int newPadding) {
      this.padding = newPadding;
    }

    public int getPadding() {
      return padding;
    }

    public void setCentersChildren(boolean newValue) {
      centersChildren = newValue;
      useOrientation = false;
    }

    public boolean getCentersChildren() {
      return centersChildren;
    }

    private int getOrientation(Container container) {
      if (!useOrientation) {
        return SwingConstants.CENTER;
      }
      if (container.getComponentOrientation().isLeftToRight()) {
        return orientation;
      }
      switch (orientation) {
        case SwingConstants.LEFT:
          return SwingConstants.RIGHT;
        case SwingConstants.RIGHT:
          return SwingConstants.LEFT;
        case SwingConstants.CENTER:
          return SwingConstants.CENTER;
      }
      return SwingConstants.LEFT;
    }

    public void addLayoutComponent(String string, Component comp) {
    }

    public void layoutContainer(Container container) {
      Component[] children = container.getComponents();

      if (children != null && children.length > 0) {
        int numChildren = children.length;
        Insets insets = container.getInsets();
        int maxWidth = 0;
        int maxHeight = 0;
        int totalButtonWidth = 0;
        int x = 0;
        int xOffset = 0;
        boolean ltr = container.getComponentOrientation().
            isLeftToRight();
        boolean reverse = (ltr) ? reverseButtons : !reverseButtons;

        for (int counter = 0; counter < numChildren; counter++) {
          Dimension pref = children[counter].getPreferredSize();
          maxWidth = Math.max(maxWidth, pref.width);
          maxHeight = Math.max(maxHeight, pref.height);
          totalButtonWidth += pref.width;
        }
        if (getSyncAllWidths()) {
          totalButtonWidth = maxWidth * numChildren;
        }
        totalButtonWidth += (numChildren - 1) * padding;

        switch (getOrientation(container)) {
          case SwingConstants.LEFT:
            x = insets.left;
            break;
          case SwingConstants.RIGHT:
            x = container.getWidth() - insets.right - totalButtonWidth;
            break;
          case SwingConstants.CENTER:
            if (getCentersChildren() || numChildren < 2) {
              x = (container.getWidth() - totalButtonWidth) / 2;
            } else {
              x = insets.left;
              if (getSyncAllWidths()) {
                xOffset = (container.getWidth() - insets.left -
                    insets.right - totalButtonWidth) /
                    (numChildren - 1) + maxWidth;
              } else {
                xOffset = (container.getWidth() - insets.left -
                    insets.right - totalButtonWidth) /
                    (numChildren - 1);
              }
            }
            break;
        }

        for (int counter = 0; counter < numChildren; counter++) {
          int index = (reverse) ? numChildren - counter - 1 :
              counter;
          Dimension pref = children[index].getPreferredSize();

          if (getSyncAllWidths()) {
            children[index].setBounds(x, insets.top,
                maxWidth, maxHeight);
          } else {
            children[index].setBounds(x, insets.top, pref.width,
                pref.height);
          }
          if (xOffset != 0) {
            x += xOffset;
          } else {
            x += children[index].getWidth() + padding;
          }
        }
      }
    }

    public Dimension minimumLayoutSize(Container c) {
      if (c != null) {
        Component[] children = c.getComponents();

        if (children != null && children.length > 0) {
          Dimension aSize;
          int numChildren = children.length;
          int height = 0;
          Insets cInsets = c.getInsets();
          int extraHeight = cInsets.top + cInsets.bottom;
          int extraWidth = cInsets.left + cInsets.right;

          if (syncAllWidths) {
            int maxWidth = 0;

            for (int counter = 0; counter < numChildren; counter++) {
              aSize = children[counter].getPreferredSize();
              height = Math.max(height, aSize.height);
              maxWidth = Math.max(maxWidth, aSize.width);
            }
            return new Dimension(extraWidth + (maxWidth * numChildren) +
                (numChildren - 1) * padding,
                extraHeight + height);
          } else {
            int totalWidth = 0;

            for (int counter = 0; counter < numChildren; counter++) {
              aSize = children[counter].getPreferredSize();
              height = Math.max(height, aSize.height);
              totalWidth += aSize.width;
            }
            totalWidth += ((numChildren - 1) * padding);
            return new Dimension(extraWidth + totalWidth, extraHeight + height);
          }
        }
      }
      return new Dimension(0, 0);
    }

    public Dimension preferredLayoutSize(Container c) {
      return minimumLayoutSize(c);
    }

    public void removeLayoutComponent(Component c) {
    }
  }


  /**
   * This class should be treated as a &quot;protected&quot; inner class.
   * Instantiate it only within subclasses of {@code BasicOptionPaneUI}.
   */
  public class PropertyChangeHandler implements PropertyChangeListener {

    /**
     * If the source of the PropertyChangeEvent <code>e</code> equals the
     * optionPane and is one of the ICON_PROPERTY, MESSAGE_PROPERTY,
     * OPTIONS_PROPERTY or INITIAL_VALUE_PROPERTY,
     * validateComponent is invoked.
     */
    public void propertyChange(PropertyChangeEvent e) {
      getHandler().propertyChange(e);
    }
  }

  /**
   * Configures any necessary colors/fonts for the specified label
   * used representing the message.
   */
  private void configureMessageLabel(JLabel label) {
    Color color = (Color) DefaultLookup.get(optionPane, this,
        "OptionPane.messageForeground");
    if (color != null) {
      label.setForeground(color);
    }
    Font messageFont = (Font) DefaultLookup.get(optionPane, this,
        "OptionPane.messageFont");
    if (messageFont != null) {
      label.setFont(messageFont);
    }
  }

  /**
   * Configures any necessary colors/fonts for the specified button
   * used representing the button portion of the optionpane.
   */
  private void configureButton(JButton button) {
    Font buttonFont = (Font) DefaultLookup.get(optionPane, this,
        "OptionPane.buttonFont");
    if (buttonFont != null) {
      button.setFont(buttonFont);
    }
  }

  /**
   * This class should be treated as a &quot;protected&quot; inner class.
   * Instantiate it only within subclasses of {@code BasicOptionPaneUI}.
   */
  public class ButtonActionListener implements ActionListener {

    protected int buttonIndex;

    public ButtonActionListener(int buttonIndex) {
      this.buttonIndex = buttonIndex;
    }

    public void actionPerformed(ActionEvent e) {
      if (optionPane != null) {
        int optionType = optionPane.getOptionType();
        Object[] options = optionPane.getOptions();

                /* If the option pane takes input, then store the input value
                 * if custom options were specified, if the option type is
                 * DEFAULT_OPTION, OR if option type is set to a predefined
                 * one and the user chose the affirmative answer.
                 */
        if (inputComponent != null) {
          if (options != null ||
              optionType == JOptionPane.DEFAULT_OPTION ||
              ((optionType == JOptionPane.YES_NO_OPTION ||
                  optionType == JOptionPane.YES_NO_CANCEL_OPTION ||
                  optionType == JOptionPane.OK_CANCEL_OPTION) &&
                  buttonIndex == 0)) {
            resetInputValue();
          }
        }
        if (options == null) {
          if (optionType == JOptionPane.OK_CANCEL_OPTION &&
              buttonIndex == 1) {
            optionPane.setValue(Integer.valueOf(2));

          } else {
            optionPane.setValue(Integer.valueOf(buttonIndex));
          }
        } else {
          optionPane.setValue(options[buttonIndex]);
        }
      }
    }
  }


  private class Handler implements ActionListener, MouseListener,
      PropertyChangeListener {

    //
    // ActionListener
    //
    public void actionPerformed(ActionEvent e) {
      optionPane.setInputValue(((JTextField) e.getSource()).getText());
    }


    //
    // MouseListener
    //
    public void mouseClicked(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }

    public void mousePressed(MouseEvent e) {
      if (e.getClickCount() == 2) {
        JList list = (JList) e.getSource();
        int index = list.locationToIndex(e.getPoint());

        optionPane.setInputValue(list.getModel().getElementAt(index));
        optionPane.setValue(JOptionPane.OK_OPTION);
      }
    }

    //
    // PropertyChangeListener
    //
    public void propertyChange(PropertyChangeEvent e) {
      if (e.getSource() == optionPane) {
        // Option Pane Auditory Cue Activation
        // only respond to "ancestor" changes
        // the idea being that a JOptionPane gets a JDialog when it is
        // set to appear and loses it's JDialog when it is dismissed.
        if ("ancestor" == e.getPropertyName()) {
          JOptionPane op = (JOptionPane) e.getSource();
          boolean isComingUp;

          // if the old value is null, then the JOptionPane is being
          // created since it didn't previously have an ancestor.
          if (e.getOldValue() == null) {
            isComingUp = true;
          } else {
            isComingUp = false;
          }

          // figure out what to do based on the message type
          switch (op.getMessageType()) {
            case JOptionPane.PLAIN_MESSAGE:
              if (isComingUp) {
                BasicLookAndFeel.playSound(optionPane,
                    "OptionPane.informationSound");
              }
              break;
            case JOptionPane.QUESTION_MESSAGE:
              if (isComingUp) {
                BasicLookAndFeel.playSound(optionPane,
                    "OptionPane.questionSound");
              }
              break;
            case JOptionPane.INFORMATION_MESSAGE:
              if (isComingUp) {
                BasicLookAndFeel.playSound(optionPane,
                    "OptionPane.informationSound");
              }
              break;
            case JOptionPane.WARNING_MESSAGE:
              if (isComingUp) {
                BasicLookAndFeel.playSound(optionPane,
                    "OptionPane.warningSound");
              }
              break;
            case JOptionPane.ERROR_MESSAGE:
              if (isComingUp) {
                BasicLookAndFeel.playSound(optionPane,
                    "OptionPane.errorSound");
              }
              break;
            default:
              System.err.println("Undefined JOptionPane type: " +
                  op.getMessageType());
              break;
          }
        }
        // Visual activity
        String changeName = e.getPropertyName();

        if (changeName == JOptionPane.OPTIONS_PROPERTY ||
            changeName == JOptionPane.INITIAL_VALUE_PROPERTY ||
            changeName == JOptionPane.ICON_PROPERTY ||
            changeName == JOptionPane.MESSAGE_TYPE_PROPERTY ||
            changeName == JOptionPane.OPTION_TYPE_PROPERTY ||
            changeName == JOptionPane.MESSAGE_PROPERTY ||
            changeName == JOptionPane.SELECTION_VALUES_PROPERTY ||
            changeName == JOptionPane.INITIAL_SELECTION_VALUE_PROPERTY ||
            changeName == JOptionPane.WANTS_INPUT_PROPERTY) {
          uninstallComponents();
          installComponents();
          optionPane.validate();
        } else if (changeName == "componentOrientation") {
          ComponentOrientation o = (ComponentOrientation) e.getNewValue();
          JOptionPane op = (JOptionPane) e.getSource();
          if (o != e.getOldValue()) {
            op.applyComponentOrientation(o);
          }
        }
      }
    }
  }

  //
  // Classes used when optionPane.getWantsInput returns true.
  //

  /**
   * A JTextField that allows you to specify an array of KeyStrokes that
   * that will have their bindings processed regardless of whether or
   * not they are registered on the JTextField. This is used as we really
   * want the ActionListener to be notified so that we can push the
   * change to the JOptionPane, but we also want additional bindings
   * (those of the JRootPane) to be processed as well.
   */
  private static class MultiplexingTextField extends JTextField {

    private KeyStroke[] strokes;

    MultiplexingTextField(int cols) {
      super(cols);
    }

    /**
     * Sets the KeyStrokes that will be additional processed for
     * ancestor bindings.
     */
    void setKeyStrokes(KeyStroke[] strokes) {
      this.strokes = strokes;
    }

    protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
        int condition, boolean pressed) {
      boolean processed = super.processKeyBinding(ks, e, condition,
          pressed);

      if (processed && condition != JComponent.WHEN_IN_FOCUSED_WINDOW) {
        for (int counter = strokes.length - 1; counter >= 0;
            counter--) {
          if (strokes[counter].equals(ks)) {
            // Returning false will allow further processing
            // of the bindings, eg our parent Containers will get a
            // crack at them.
            return false;
          }
        }
      }
      return processed;
    }
  }


  /**
   * Registered in the ActionMap. Sets the value of the option pane
   * to <code>JOptionPane.CLOSED_OPTION</code>.
   */
  private static class Actions extends UIAction {

    private static final String CLOSE = "close";

    Actions(String key) {
      super(key);
    }

    public void actionPerformed(ActionEvent e) {
      if (getName() == CLOSE) {
        JOptionPane optionPane = (JOptionPane) e.getSource();

        optionPane.setValue(Integer.valueOf(JOptionPane.CLOSED_OPTION));
      }
    }
  }


  /**
   * This class is used to create the default buttons. This indirection is
   * used so that addButtonComponents can tell which Buttons were created
   * by us vs subclassers or from the JOptionPane itself.
   */
  private static class ButtonFactory {

    private String text;
    private int mnemonic;
    private Icon icon;
    private int minimumWidth = -1;

    ButtonFactory(String text, int mnemonic, Icon icon, int minimumWidth) {
      this.text = text;
      this.mnemonic = mnemonic;
      this.icon = icon;
      this.minimumWidth = minimumWidth;
    }

    JButton createButton() {
      JButton button;

      if (minimumWidth > 0) {
        button = new ConstrainedButton(text, minimumWidth);
      } else {
        button = new JButton(text);
      }
      if (icon != null) {
        button.setIcon(icon);
      }
      if (mnemonic != 0) {
        button.setMnemonic(mnemonic);
      }
      return button;
    }

    private static class ConstrainedButton extends JButton {

      int minimumWidth;

      ConstrainedButton(String text, int minimumWidth) {
        super(text);
        this.minimumWidth = minimumWidth;
      }

      public Dimension getMinimumSize() {
        Dimension min = super.getMinimumSize();
        min.width = Math.max(min.width, minimumWidth);
        return min;
      }

      public Dimension getPreferredSize() {
        Dimension pref = super.getPreferredSize();
        pref.width = Math.max(pref.width, minimumWidth);
        return pref;
      }
    }
  }
}
